package filesystem;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;

public class Table {

	String name;
	int rowCount;

	public enum DataTypes {
		INT, CHAR, VARCHAR
	};

	public ArrayList<DataTypes> attributes;
	public ArrayList<Integer> attributeLengths;

	String basePath;

	public Table(String name, String basePath, ArrayList<DataTypes> attributes,
			ArrayList<Integer> attributeLengths) {
		super();
		this.name = name;
		this.basePath = basePath;
		this.rowCount = 0;
		this.attributes = attributes;
		this.attributeLengths = attributeLengths;
	}

	public Table(String name, String basePath) {
		this.name = name;
		this.basePath = basePath;
		this.rowCount = 0;
	}

	public void saveAttributes() {

		// create folder
		String filePath = basePath + name;
		File folder = new File(filePath);
		folder.mkdir();

		// save attributes
		File attrFile = new File(filePath + "/attributes");
		try {
			if (!attrFile.exists()) {
				attrFile.createNewFile();
			}
			FileWriter fw = new FileWriter(attrFile);
			BufferedWriter bw = new BufferedWriter(fw);

			for (int i = 0; i < attributes.size(); i++) {
				DataTypes dType = attributes.get(i);
				switch (dType) {
				case INT:
					bw.write("int; ");
					break;
				case CHAR:
					bw.write("char; ");
					break;
				case VARCHAR:
					int varLength = attributeLengths.get(i);
					bw.write("varchar|" + varLength + "; ");
					break;
				}
			}
			bw.flush();
			bw.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean drop() {
		String filePath = basePath + name;
		File folder = new File(filePath);
		return delDir(folder);
	}

	public boolean addRow(ArrayList<Object> rowData) {
		rowCount++;

		String filePath = basePath + name;
		File dataFile = new File(filePath + "/data");
		try {
			if (!dataFile.exists()) {
				dataFile.createNewFile();
			}

			Writer output;
			output = new BufferedWriter(new FileWriter(dataFile, true));

			// always add the 'active' one
			output.write("1; ");

			// for every column
			for (int i = 0; i < rowData.size(); i++) {
				DataTypes dType = attributes.get(i);
				Object element = rowData.get(i);
				switch (dType) {
				case INT:
					if (!(element instanceof Integer)) {
						//throw new IllegalArgumentException();
						return false;
					} else {
						output.write(((Integer) element).toString() + "; ");
					}
					break;
				case CHAR:
					if (!(element instanceof Character)) {
						//throw new IllegalArgumentException();
						return false;
					} else {
						output.write((Character) element + "; ");
					}
					break;
				case VARCHAR:
					if (!(element instanceof String)) {
						//throw new IllegalArgumentException();
						return false;
					} else {
						String str = (String) element;
						int varLength = attributeLengths.get(i);
						if (str.length() >= varLength) {
							output.write(str, str.length() - varLength,
									varLength);
						} else {

							while (varLength - str.length() > 0) {
								str += '\0';
							}
							output.write(str);
						}
						output.write("; ");
					}
					break;

				}
			}
			output.write('\n');
			output.flush();
			output.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
		return true;
	}

	public boolean deleteRow(int rowNr) {
		if (rowNr > rowCount) {
			// trying to erase line that doesn't exist
			return false;
		}

		String filePath = basePath + name;
		File dataFile = new File(filePath + "/data");
		try {
			BufferedReader br = new BufferedReader(new FileReader(dataFile));
			File tmpFile = new File(filePath + "/data.tmp");
			BufferedWriter bw = new BufferedWriter(new FileWriter(tmpFile));

			String line = br.readLine();
			int rowCounter = 0;
			while (line != null && rowCounter < rowNr) {
				bw.write(line + "\n");
				line = br.readLine();
				rowCounter++;
			}
			if (line == null) {
				// reached end of file before rowNr. should not happen.
				return false;
			} else {
				// reached row that will be deleted
				if (line.charAt(0) != '1') {
					// cannot delete already deleted row
					return false;
				}
				bw.write("0" + line.substring(1) + "\n");
				line = br.readLine();
			}

			// copy rest of file
			while (line != null) {
				bw.write(line + "\n");
				line = br.readLine();
			}
			bw.flush();
			bw.close();

			// make tmp file permanent
			tmpFile.renameTo(dataFile);

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return true;
	}

	public ArrayList<ArrayList<Object>> selectTable() {
		ArrayList<Object> row = new ArrayList<Object>();
		ArrayList<ArrayList<Object>> result = new ArrayList<ArrayList<Object>>();

		String filePath = basePath + name;
		File dataFile = new File(filePath + "/data");
		try {
			BufferedReader br = new BufferedReader(new FileReader(dataFile));

			String line = br.readLine();
			while (line != null) {
				row = parseObjects(line);
				if (row != null) {
					result.add(row);
				}
				line = br.readLine();
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}

	private ArrayList<Object> parseObjects(String line) {
		if (line.charAt(0) != '1') // row was deleted
			return null;

		ArrayList<Object> result = new ArrayList<Object>();
		int beg, end, clmnIdx;
		clmnIdx = 0;
		beg = line.indexOf(';'); // jump over active/deleted flag
		String sub = line.substring(beg + 2);
		end = sub.indexOf(';');
		while (end != -1) {
			DataTypes dType = attributes.get(clmnIdx);
			switch (dType) {
			case INT:
				result.add(Integer.valueOf(sub.substring(0, end)));
				break;
			case CHAR:
				result.add(Character.valueOf(sub.charAt(0)));
				break;
			case VARCHAR:
				int varLength = attributeLengths.get(clmnIdx);
				result.add(sub.substring(beg, beg + varLength));
				break;
			}

			beg = end;
			sub = sub.substring(beg + 2);
			end = sub.indexOf(';');
			clmnIdx++;
		}

		return result;
	}

	public static Table createNewTable(String name, String path,
			ArrayList<DataTypes> attributes, ArrayList<Integer> attributeLengths)
			throws IllegalArgumentException {
		File attrFile = new File(path + name + "/attributes");
		if (attrFile.exists()) {
			throw new IllegalArgumentException("Table name already exists");
		}
		Table result = new Table(name, path, attributes, attributeLengths);
		result.saveAttributes();

		return result;
	}

	public static Table readTableFromDisk(String name, String basePath) {
		Table result = new Table(name, basePath);

		File attrFile = new File(basePath + name + "/attributes");
		try {
			if (!attrFile.exists()) {
				return null;
			}
			FileReader fr = new FileReader(attrFile);
			BufferedReader br = new BufferedReader(fr);
			String line = br.readLine();

			result.attributes = new ArrayList<DataTypes>();
			result.attributeLengths = new ArrayList<Integer>();

			int beg, end, clmnIdx;
			clmnIdx = 0;
			beg = 0;
			end = line.indexOf(';');
			String restline = line.substring(beg);;
			while (end > -1) {
				String sub = restline.substring(0, end);
				if (sub.equalsIgnoreCase("int")) {
					result.attributes.add(DataTypes.INT);
					result.attributeLengths.add(0);
				} else if (sub.equalsIgnoreCase("char")) {
					result.attributes.add(DataTypes.CHAR);
					result.attributeLengths.add(1);
				} else if (sub.toLowerCase().contains("varchar")) {
					result.attributes.add(DataTypes.VARCHAR);
					int mid = sub.indexOf('|');
					result.attributeLengths.add(Integer.valueOf(sub.substring(
							mid + 1, end)));
				}

				beg = end+2;
				restline = restline.substring(beg);
				end = restline.indexOf(';');
				clmnIdx++;
			}

		} catch (IOException e) {
			e.printStackTrace();
		}

		File dataFile = new File(basePath + name + "/data");
		if (dataFile.exists()) {
			try {
				FileReader fr = new FileReader(dataFile);
				BufferedReader br = new BufferedReader(fr);

				while (br.readLine() != null) {
					result.rowCount++;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
		
		System.out.println("Read Table \'"+name+"\' from disk. (Nr of attributes:"+result.attributes.size()+")");

		return result;
	}

	public static boolean dropTable(Table table) {
		boolean result = table.drop();
		if (result)
			table = null;

		return result;
	}

	public static boolean dropTable(String tableName, String basePath) {
		String filePath = basePath + tableName;
		File folder = new File(filePath);
		return delDir(folder);
	}

	private static boolean delDir(File dir) {
		if (dir.isDirectory()) {
			String[] children = dir.list();
			for (int i = 0; i < children.length; i++) {
				boolean success = delDir(new File(dir, children[i]));
				if (!success)
					return false;
			}
		}
		// directory now empty
		return dir.delete();
	}

}
